home *** CD-ROM | disk | FTP | other *** search
/ Shareware Grab Bag / Shareware Grab Bag.iso / 007 / async.arc / ASYNC.PAS next >
Pascal/Delphi Source File  |  1987-03-23  |  15KB  |  330 lines

  1. (**********************************************************************)
  2. (*                     ASYNCHRONOUS I/O ROUTINES                      *)
  3. (*                       Version 1.01, 4/8/85                         *)
  4. (*                                                                    *)
  5. (* The following are data definitions and procedures which perform    *)
  6. (* I/O functions for IBM PC asynchronous communications adapters.     *)
  7. (* Comments here and there, below, explain what's what.  Enjoy!       *)
  8. (*                                                                    *)
  9. (* Although these routines are set-up to support COM1 and COM2, which *)
  10. (* may be open simultaneously if you wish, it's probably not too much *)
  11. (* of an effort to make them support more - like in the PC/AT.  Also, *)
  12. (* it's a trivial excercise to support up to 9600 baud - presuming    *)
  13. (* the machine is fast enough.  You might also consider adding such   *)
  14. (* stuff as XON/XOFF pacing, port-to-port communication, and so on.   *)
  15. (*                                                                    *)
  16. (* In any case, writing this took a bunch of groping around, head     *)
  17. (* scratching, trial and error, plus cursing at IBM's inadequate and  *)
  18. (* obscure documentation on the subject.  It's almost like they don't *)
  19. (* want the general public to write stuff like this.  Too bad, IBM!   *)
  20. (*                                                                    *)
  21. (* Written and placed in the public domain by:                        *)
  22. (*                       Glen F. Marshall                             *)
  23. (*                       1006 Gwilym Circle                           *)
  24. (*                       Berwyn, PA 19312                             *)
  25. (**********************************************************************)
  26.  
  27. type
  28.   ComPort      = (Com1,Com2);                     { Asynchronous ports }
  29.   ComSpeed     = (LowSpeed,MedSpeed,HighSpeed);   { 300/1200/2400 baud }
  30.   ComParity    = (NoParity,EvenParity,OddParity); { no/even/odd parity }
  31.   ComInBuffer  = array[0..4095] of byte;          { 4K ring buffer     }
  32.   ComOutBuffer = array[0..4095] of byte;          { 4K ring buffer     }
  33.   ComCtlPtr    = ^ComCtlRec;                      { Ptr, port control  }
  34.   ComCtlRec    = record
  35.                    ComCtlSpeed:      ComSpeed;    { current baud rate  }
  36.                    ComCtlParity:     ComParity;   { current parity     }
  37.                    LastComIdent:     byte;        { interrupt reason   }
  38.                    LastComLineStat:  byte;        { line status        }
  39.                    LastComModemStat: byte;        { modem status       }
  40.                    SavedVector:      ^byte;       { pre-open vector    }
  41.                    InputInIx:        integer;     { ring buffer index  }
  42.                    InputOutIx:       integer;     { ring buffer index  }
  43.                    OutputOutIx:      integer;     { ring buffer index  }
  44.                    OutputInIx:       integer;     { ring buffer index  }
  45.                    InputBuffer:      ComInBuffer; { input ring buffer  }
  46.                    OutputBuffer:     ComOutBuffer;{ output ring buffer }
  47.                  end;
  48.  
  49. const
  50.   AsyncDS:     integer = -1; { NOTE: contrary to the idea of constants,
  51.                                but to give the interrupt handler access
  52.                                to the ComCtl array, this constant is
  53.                                modified.  See StoreDS. }
  54.   ComInt:      array[Com1..Com2] of byte = ($C,$B);
  55.   ComData      = 0;       { Communications port data xmit/recv      }
  56.   ComEnable    = 1;       { Communications port interrupt enable    }
  57.   ComEnableRD  = 1;       {   data received                         }
  58.   ComEnableTX  = 2;       {   transmit register empty               }
  59.   ComEnableLS  = 4;       {   line status                           }
  60.   ComEnableMS  = 8;       {   modem status                          }
  61.   ComIdent     = 2;       { Communications port interrupt identity  }
  62.   ComIdentNot  = 1;       {   interrupt not pending                 }
  63.   ComIdentMS   = 0;       {   modem status interrupt                }
  64.   ComIdentTX   = 2;       {   transmit register empty               }
  65.   ComIdentRD   = 4;       {   data received                         }
  66.   ComIdentLS   = 6;       {   line status interrupt                 }
  67.   ComLineCtl   = 3;       { Communications port line control        }
  68.   ComLineInit: array[NoParity..OddParity] of byte = ($03,$1A,$0A);
  69.   ComLineBreak = 64;      { Communications Line Send Break          }
  70.   ComLineDLAB  = 128;     { Communications Divisor Latch Access Bit }
  71.   ComModemCtl  = 4;       { Communications port modem control       }
  72.   ComModemDTR  = 1;       {   data terminal ready                   }
  73.   ComModemRTS  = 2;       {   request to send                       }
  74.   ComModemOUT1 = 4;       {   Out 1 signal (enables ring)           }
  75.   ComModemOUT2 = 8;       {   Out 2 signal (enables data receive)   }
  76.   ComLineStat  = 5;       { Communications port line status         }
  77.   ComLineOR    = 2;       {   overrun                               }
  78.   ComLinePE    = 4;       {   parity error                          }
  79.   ComLineFE    = 8;       {   framing error                         }
  80.   ConLineBI    = 16;      {   break interrupt                       }
  81.   ComLineTHRE  = 32;      {   transmit holding register empty       }
  82.   ComModemStat = 6;       { Communications port modem status        }
  83.   ComModemCTS  = 16;      {   clear to send                         }
  84.   ComModemDSR  = 32;      {   data set ready                        }
  85.   ComModemRI   = 64;      {   phone ring                            }
  86.   ComModemCD   = 128;     {   carrier detect                        }
  87.   ComDivLo     = 0;       { Communications port baud-rate divisor 1 }
  88.   ComDivHi     = 1;       { Communications port baud-rate divisor 2 }
  89.   ComBaudDiv:  array[LowSpeed..HighSpeed] of integer = (384,96,48);
  90.   Cmd_8259     = $20;     { 8259 interrupt controller command port  }
  91.   EOI_8259     = $20;     {   "End Of Interrupt" command            }
  92.   RIL_8259     = $0B;     {   "Report Interrupt Level" command      }
  93.   Msk_8259     = $21;     { 8259 interrupt controller mask port     }
  94.   MskVal_8259: array[Com1..Com2] of byte = ($EF,$F7);
  95.  
  96. var
  97.   ComBase: array[Com1..Com2] of integer absolute $0040:0000;
  98.   ComPtr:  array[Com1..Com2] of ComCtlPtr; { for each comm port }
  99.  
  100. { This is called to modify AsyncDS }
  101. procedure StoreDS(var SavedDS: integer);
  102.   begin
  103.     savedDS := DSEG;
  104.   end;
  105.  
  106. { This turns the communications interrupts on or off }
  107. procedure Set_8259(ComDev: ComPort; Sw: boolean);
  108.   begin
  109.     inline($FA); {stop interrupts while we do this}
  110.     case Sw of
  111.       true:  port[Msk_8259] :=
  112.                  port[Msk_8259] and MskVal_8259[ComDev];
  113.       false: port[Msk_8259] :=
  114.                  port[Msk_8259] or ($FF xor MskVal_8259[ComDev]);
  115.     end;
  116.     inline($FB); {reenable interrupts}
  117.   end {Set_8259};
  118.  
  119. { Communications interrupt handler: do NOT call this! }
  120. procedure AsyncInterrupt;
  121.   var
  122.     pushes: array[1..8] of integer; {allows for inline pushes}
  123.     ComDev: ComPort;
  124.   begin
  125.     inline($83/$C4/$11/           { add  sp,11h (fool turbo) }
  126.            $50/                   { push ax                  }
  127.            $53/                   { push bx                  }
  128.            $51/                   { push cx                  }
  129.            $52/                   { push dx                  }
  130.            $56/                   { push si                  }
  131.            $57/                   { push di                  }
  132.            $1E/                   { push ds                  }
  133.            $06/                   { push es                  }
  134.            $4C/                   { dec  sp     (fool turbo) }
  135.            $2E/$8E/$1E/AsyncDS);  { mov  ds,CS:AsyncDS       }
  136.     port[Cmd_8259] := RIL_8259; { find out which COM port interrupted }
  137.     if (port[Cmd_8259] and ($FF xor MskVal_8259[Com1])) <> 0
  138.       then ComDev := Com1
  139.       else ComDev := Com2;
  140.     with ComPtr[ComDev]^ do { Having deduced the port, find out why }
  141.     begin
  142.       LastComIdent := Port[ComBase[ComDev]+ComIdent];
  143.       if (LastComIdent and ComIdentNot) = 0 then { There is a reason }
  144.       begin
  145.         case LastComIdent of
  146.           ComIdentLS: LastComLineStat :=
  147.                         port[ComBase[ComDev]+ComLineStat];
  148.           ComIdentMS: LastComModemStat :=
  149.                         port[ComBase[ComDev]+ComModemStat];
  150.           ComIdentRD: begin
  151.                         if ((InputInIx+1) mod sizeof(InputBuffer))
  152.                         <> InputOutIx then
  153.                         begin { store input character in ring buffer }
  154.                           InputBuffer[InputInIx] :=
  155.                             port[ComBase[ComDev]+ComData];
  156.                           InputInIx :=
  157.                             (InputInIx+1) mod sizeof(InputBuffer);
  158.                         end
  159.                         else LastComLineStat := { buffer overrun }
  160.                                LastComLineStat or ComLineOR;
  161.                       end;
  162.           ComIdentTX: if OutputOutIx <> OutputInIx then
  163.                       begin { write output character from ring buffer }
  164.                         port[ComBase[ComDev]+ComData] :=
  165.                           OutputBuffer[OutputOutIx];
  166.                         OutputOutIx :=
  167.                           (OutputOutIx+1) mod sizeof(OutputBuffer);
  168.                       end;
  169.         end;
  170.       end;
  171.     end;
  172.     port[Cmd_8259] := EOI_8259;
  173.     inline($44/                   { inc  sp    (unfool turbo) }
  174.            $07/                   { pop  es                   }
  175.            $1F/                   { pop  ds                   }
  176.            $5F/                   { pop  di                   }
  177.            $5E/                   { pop  si                   }
  178.            $5A/                   { pop  dx                   }
  179.            $59/                   { pop  cx                   }
  180.            $5B/                   { pop  bx                   }
  181.            $58/                   { pop  ax                   }
  182.            $8B/$E5/               { mov  sp,bp                }
  183.            $5D/                   { pop  bp                   }
  184.            $CF);                  { iret                      }
  185.   end {AsyncInterrupt};
  186.  
  187. { This modifies the communications interrupt vector }
  188. procedure SetInterrupt(ComDev: ComPort; Sw: boolean);
  189.   var
  190.     MsDosRegs: record
  191.                  AX, BX, CX, DX, BP, SI, DI, DS, ES, Flags: integer;
  192.                end;
  193.   begin
  194.     inline($FA); {stop interrupts while we do this}
  195.     with MsDosRegs, ComPtr[ComDev]^ do
  196.     begin
  197.       case Sw of
  198.         true:  begin
  199.                  AX := $3500+ComInt[ComDev]; { get interrupt vector}
  200.                  MsDos(MsDosRegs);
  201.                  SavedVector := Ptr(ES,BX);
  202.                  DS := CSEG;
  203.                  DX := ofs(AsyncInterrupt);
  204.                end;
  205.         false: begin
  206.                  DS := seg(SavedVector^);
  207.                  DX := ofs(SavedVector^);
  208.                end;
  209.       end;
  210.       AX := $2500+ComInt[ComDev]; { set interrupt vector }
  211.       MsDos(MsDosRegs)
  212.     end;
  213.     inline($FB); {reenable interrupts}
  214.   end {SetInterrupt};
  215.  
  216. { This sets the communications baud rate }
  217. procedure SetSpeed(ComDev: ComPort; ComRate: ComSpeed);
  218.   begin
  219.     inline($FA); {stop interrupts while we do this}
  220.     with ComPtr[ComDev]^ do ComCtlSpeed  := ComRate;
  221.     port[ComBase[ComDev]+ComLineCtl] :=
  222.       port[ComBase[ComDev]+ComLineCtl] or ComLineDLAB;
  223.     port[ComBase[ComDev]+ComDivLo] := lo(ComBaudDiv[ComRate]);
  224.     port[ComBase[ComDev]+ComDivHi] := hi(ComBaudDiv[ComRate]);
  225.     port[ComBase[ComDev]+ComLineCtl] :=
  226.       port[ComBase[ComDev]+ComLineCtl] and ($FF xor ComLineDLAB);
  227.     inline($FB); {reenable interrupts}
  228.   end {SetSpeed};
  229.  
  230. { This modifies the line control register for parity & data bits }
  231. procedure SetFormat(ComDev: ComPort; ComFormat: ComParity);
  232.   begin
  233.     inline($FA); {stop interrupts while we do this}
  234.     with ComPtr[ComDev]^ do ComCtlParity := ComFormat;
  235.     port[ComBase[ComDev]+ComLineCtl] := ComLineInit[ComFormat];
  236.     inline($FB); {reenable interrupts}
  237.   end {SetFormat};
  238.  
  239. { This modifies the modem control register for DTR, RTS, etc. }
  240. procedure SetModem(ComDev: ComPort; ModemCtl: byte);
  241.   begin
  242.     inline($FA); {stop interrupts while we do this}
  243.     port[ComBase[ComDev]+ComModemCtl] := ModemCtl;
  244.     inline($FB); {reenable interrupts}
  245.   end {SetModem};
  246.  
  247. { This modifies the communications interrupt enable register }
  248. procedure SetEnable(ComDev: ComPort; Enable: byte);
  249.   begin
  250.     inline($FA); {stop interrupts while we do this}
  251.     port[ComBase[ComDev]+ComEnable] := Enable;
  252.     inline($FB); {reenable interrupts}
  253.   end {SetModem};
  254.  
  255. { This procedure MUST be called before doing any I/O. }
  256. procedure ComOpen(ComDev: ComPort;
  257.                   ComRate: ComSpeed; ComFormat: ComParity);
  258.   begin
  259.     Set_8259(ComDev,false);
  260.     StoreDS(AsyncDS);
  261.     new(ComPtr[ComDev]);
  262.     with ComPtr[ComDev]^ do
  263.     begin
  264.       LastComIdent := 1;
  265.       InputInIx    := 0;
  266.       InputOutIx   := 0;
  267.       OutputOutIx  := 0;
  268.       OutputInIx   := 0;
  269.     end;
  270.     SetInterrupt(ComDev,true);
  271.     Set_8259(ComDev,true);
  272.     SetEnable(ComDev, ComEnableRD+ComEnableTX+ComEnableLS+ComEnableMS);
  273.     SetModem(ComDev, ComModemDTR+ComModemRTS+ComModemOUT1+ComModemOUT2);
  274.     SetSpeed(ComDev, ComRate);
  275.     SetFormat(ComDev, ComFormat);
  276.     with ComPtr[ComDev]^ do
  277.     begin
  278.       LastComLineStat  := port[ComBase[ComDev]+ComLineStat];
  279.       LastComModemStat := port[ComBase[ComDev]+ComModemStat];
  280.     end;
  281.   end {ComOpen};
  282.  
  283. { This procedure shuts-down communications }
  284. procedure ComClose(ComDev: ComPort);
  285.   begin
  286.     Set_8259(ComDev,false);
  287.     SetEnable(ComDev, $00);
  288.     SetInterrupt(ComDev, false);
  289.     SetModem(ComDev, $00);
  290.     port[ComBase[ComDev]+ComLineCtl] := $00;
  291.     dispose(ComPtr[ComDev]);
  292.   end {ComClose};
  293.  
  294. { This procedure tells you if there's any input }
  295. function ComInReady(ComDev: ComPort): boolean;
  296.   begin
  297.     with ComPtr[ComDev]^ do ComInReady := InputInIx <> InputOutIx;
  298.   end {ComInReady};
  299.  
  300. { This procedure reads input from the ring buffer }
  301. function ComIn(ComDev: ComPort): char;
  302.   begin
  303.     with ComPtr[ComDev]^ do
  304.     begin
  305.       repeat until ComInReady(ComDev);
  306.       ComIn := chr(InputBuffer[InputOutIx]);
  307.       InputOutIx := (InputOutIx+1) mod sizeof(InputBuffer);
  308.     end;
  309.   end {ComIn};
  310.  
  311. { This procedure writes output, filling the ring buffer if necessary. }
  312. procedure ComOut(ComDev: ComPort; Data: char);
  313.   begin
  314.     with ComPtr[ComDev]^ do
  315.     begin
  316.       if ((port[ComBase[ComDev]+ComLineStat] and ComLineTHRE) <> 0) and
  317.          (OutputInIx = OutputOutIx) then
  318.       begin
  319.         port[ComBase[ComDev]+ComData] := ord(Data);
  320.       end
  321.       else
  322.       begin
  323.         repeat
  324.         until ((OutputInIx+1) mod sizeof(outputBuffer)) <> OutputOutIx;
  325.         OutputBuffer[OutputInIx] := ord(Data);
  326.         OutputInIx := (OutputInIx+1) mod sizeof(OutputBuffer);
  327.       end;
  328.     end;
  329.   end {ComOut};
  330.